﻿//
// protocol.hpp
// ~~~~~~~~
//

#include <locale>

#ifndef YUTOPP_PROTOCOL_HPP
#define YUTOPP_PROTOCOL_HPP

#include "placeholders.hpp"
#include "error.hpp"

#include "utility/utility.hpp"

#include <string>
#include <map>
#include <sstream>
#include <tuple>

#include <boost/asio.hpp>
#include <boost/noncopyable.hpp>

#ifdef OLC_OAUTH_USE_SSL
# include <boost/asio/ssl.hpp>
#endif

#include <boost/thread.hpp>
#include <boost/bind.hpp>
#include <boost/function.hpp>
#include <boost/optional.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/make_shared.hpp>
#include <boost/enable_shared_from_this.hpp>

namespace webapp
{
	namespace protocol
	{
		namespace detail
		{
			class attribute
			{
			public:
				attribute()
					: is_chunked_( false )
				{}

				attribute( bool chunked )
					: is_chunked_( chunked )
				{}

				void set_chunked( bool chunked ) { is_chunked_ = chunked; }
				bool is_chunked() const { return is_chunked_; }

			private:
				bool is_chunked_;
			};

			void throw_exception( const error::value& e )
			{
				throw error::exception()
					<< error::code_info( e.get_code() )
					<< error::message_info( e.get_message() )
					<< error::status_info( e.get_status() );
			}

			//
			template<class Stream>
			boost::optional<error::value> check_response_code( Stream& ss )
			{
				using namespace error;

				std::string line;
				if ( !getline( ss, line ) )
					return value( error::critical, "couldn't read response." );

				if ( line.size() < 12 )
					return value( error::critical, "invalid response." );
			
				//
				const std::string protocol = line.substr( 0, 5 );
				//レスポンスがおかしかったら
				if ( protocol != "HTTP/" )
					return value( error::critical, "invalid protocol." );

				const std::string code = line.substr( 9, 3 );
				
				std::cout << code << std::endl;	//////////////////////////////////////////

				//ステータスコードが200番台以外なら
				if ( code.at(0) != '2' )
					return value( http_status, "status code.", boost::lexical_cast<int>( code ) );

				return boost::none;
			}
			
			//仮
			template<class Stream>
			boost::optional<error::value> check_header( Stream& ss, attribute& attr )
			{
				std::string line, buf;
				while( getline( ss, line ) ) {
					if ( line == "\r" ) {
						find_attribute( buf, attr );
						return boost::none;
					}

					buf += line + "\n";
				}
				return error::value( error::critical, "invalid header." );
			}

			//仮
			void find_attribute( const std::string& buffer, attribute& attr )
			{
				//ちょっとこれはないわ
				attr.set_chunked( buffer.find( "Transfer-Encoding: chunked" ) != std::string::npos );

				return;
			}

			//
			template<class Stream>
			std::string inline read_body( Stream& ss )
			{
				std::string line, buf;
				while( getline( ss, line ) )
					buf += line + "\n";

				return buf;
			}

			//
			bool cut_header( std::stringstream& ss )
			{
				const std::string buffer = ss.str();
				std::size_t point = 0;
				for(;;) {
					const std::size_t now = buffer.find( "\r\n", point );
					if ( now != std::string::npos ) {
						if ( now - point == 0 ) {
							point = now + 2;
							break;
						}
						point = now + 2;
					} else {
						return false;
					}
				}

				ss.seekp( point, std::ios::cur );

				//ss.str( "" );
				//ss << std::string( buffer.cbegin() + point, buffer.cend() );

				return true;
			}

			//チャンクの接合
			// - 良いか！へんな値を渡すんじゃないぞ！
			std::string cat_chunk( std::stringstream& ss )
			{	
				const std::string buffer = ss.str();
				ss.str( "" );
				ss.seekp( 0, std::ios::cur );

				std::string ret;
				std::size_t cursor = 0;
				for(;;) {
					const auto crlf = buffer.find( "\r\n", cursor );
					if ( crlf != std::string::npos ) {
						const std::size_t chunk = util::hex_to_dec( buffer.substr( cursor, crlf - cursor ) );
						//std::cout << "hex : " << buffer.substr( cursor, crlf - cursor ) << " dec: " << chunk << " last : " << ( buffer.size() - ( crlf + 2 ) ) << std::endl;

						if ( chunk == 0 ) {
							cursor = crlf + 2;

						} else if ( chunk < ( buffer.size() - ( crlf + 2 ) ) ) {
							cursor = crlf + 2;
							ret += std::string( buffer.cbegin() + cursor, buffer.cbegin() + cursor + chunk );
							cursor += chunk;

						} else {
							// 余りが出たらストリームに戻す
							ss << std::string( buffer.cbegin() + cursor, buffer.cend() );
							break;
						}
					} else {
						//
						break;
					}
				}

				return ret;
			}

			template<typename T>
			void inline make_http_request(
				std::stringstream& ss,
				const std::string& method,
				const std::string& path,
				const T& parameter,
				const std::string& body )
			{
				ss << method << " " << path << " HTTP/1.1\r\n";
				BOOST_FOREACH( const typename T::value_type& v, parameter ) {
					ss << v.first << ": " << v.second << "\r\n";
				}
				ss << "\r\n" << body << std::flush;
			}
		}

		class post_impl
		{
		public:
			post_impl( boost::asio::io_service& io, boost::shared_ptr<std::string> data, const bool always_call )
				: resolver_( io )
				, data_( data )
				, is_always_call_( always_call )
				, is_checked_response_code_( false )
				, is_parsed_header_( false )
			{}

			template<typename ResolveHandler>
			void resolve(
				const boost::asio::ip::tcp::resolver::query& query,
				ResolveHandler& handler )
			{
				resolver_.async_resolve( query, handler );
			}

			template<typename AsyncWriteStream, typename WriteHandler>
			void write(
				AsyncWriteStream& stream,
				WriteHandler& handler ) const
			{
				boost::asio::async_write(
					stream,
					boost::asio::buffer( *data_ ), 
					handler
					);
			}

			template<typename AsyncReadStream, typename ReadHandler>
			void read(
				AsyncReadStream& stream,
				ReadHandler& handler )
			{
				boost::asio::async_read(
					stream,
					response_,
					boost::asio::transfer_at_least( 1 ),
					handler
					);
			}

			template<typename Handler>
			void notify_error( const error::value& error, Handler& handler ) const
			{
				handler( "", error );
			}

			template<typename Handler>
			void call( Handler& handler )
			{
				error::value err;

				if ( !is_checked_response_code_ ) {
					//
					if ( const boost::optional<error::value> e = detail::check_response_code( buffer_ ) ) {
						err |= *e;
						if ( err.get_code() > error::http_status ) {
							notify_error( err, handler );
							return;
						}
					}
					is_checked_response_code_ = true;
				}

				if ( !is_parsed_header_ ) {
					//ヘッダ
					if ( const boost::optional<error::value> e = detail::check_header( buffer_, attribute_ ) ) {
						err |= *e;
						if ( err.get_code() == error::critical ) {
							notify_error( err, handler );
							return;
						}
					}
					//detail::cut_header( buffer_ );
					is_parsed_header_ = true;
				}

				if ( is_parsed_header_ ) {
					// 
					if ( attribute_.is_chunked() ) {
						handler( detail::cat_chunk( buffer_ ), error::value() );
					} else {
						handler( detail::read_body( buffer_ ), error::value() );
					}
				}
			};

			void write_to()
			{
				buffer_.write( boost::asio::buffer_cast<const char*>( response_.data() ), response_.size() );
				response_.consume( response_.size() );
			}

			bool is_always_call() const { return is_always_call_; }

		private:
			boost::asio::ip::tcp::resolver resolver_;
			boost::asio::streambuf response_;
			std::stringstream buffer_;

			boost::shared_ptr<std::string> data_;
			bool is_always_call_, is_checked_response_code_, is_parsed_header_;
			detail::attribute attribute_;
		};

		//タグ
		struct sync_connection {};
		struct async_connection {};

		template<class T>
		struct is_sync_connection
			: boost::is_same<typename T::connection_type, sync_connection>
		{};

		//HTTP
		// 仮
		namespace http
		{
			class sync
			{
			public:
				typedef sync_connection						connection_type;
				typedef std::map<std::string, std::string>	parameter_type;

				std::string operator()(
					const std::string& domain,
					const std::string& method,
					const std::string& path,
					const parameter_type& parameter,
					const std::string& body,
					error::value& error ) const
				{
					try {
						std::stringstream src;

						detail::make_http_request<parameter_type>(
							src,
							method,
							path,
							parameter,
							body );

						std::cout << "-------------------------------" << std::endl;
						std::cout << "path : " << path << std::endl;
						std::cout << src.str() << std::endl;
						std::cout << "-------------------------------" << std::endl;

						boost::asio::ip::tcp::iostream ns( domain, name() );

						//送信
						ns << src.str() << std::flush;

						//
						if ( const boost::optional<error::value> e = detail::check_response_code( ns ) ) {
							error |= *e;
							if ( error.get_code() > error::http_status )
								return "";
						}
				
						//ヘッダ
						detail::attribute attr;
						if ( const boost::optional<error::value> e = detail::check_header( ns, attr ) ) {
							error |= *e;
							if ( error.get_code() == error::critical )
								return "";
						}
				
						// 
						if ( attr.is_chunked() ) {
							std::cout << "chunked!" << std::endl;

							src.str( "" );
							src << &ns;
							return detail::cat_chunk( src );

						} else {
							return detail::read_body( ns );
						}
					}
					catch(...)
					{
						error = error::value( error::critical );
						return "";
					}
				}

				static const char* name()
				{
					return "http";
				}
			};

			class async
			{
			public:
				typedef async_connection					connection_type;
				typedef std::map<std::string, std::string>	parameter_type;

				async()
					: work_( new boost::asio::io_service::work( io_ ) )
					, io_runner_( boost::bind( &boost::asio::io_service::run, &io_ ) )
				{}

				~async()
				{
					try {
						work_.reset();
						io_.stop();
					
						io_runner_.join();
						io_runner_.detach();
						io_.reset();
					}
					catch(...) {}
				}

				template<typename Handler>
				void operator()(
					const std::string& domain,
					const std::string& method,
					const std::string& path,
					const parameter_type& parameter,
					const std::string& body,
					Handler& handler,
					const bool always_call = false )
				{
					typedef post<Handler> post_type;

					try {
						std::stringstream src;
						detail::make_http_request<parameter_type>(
							src,
							method,
							path,
							parameter,
							body
							);

						boost::shared_ptr<std::string> data =
							boost::make_shared<std::string>(
								src.str()
								);

						boost::shared_ptr<post_type> post_object =
							boost::make_shared<post_type>(
								boost::ref( io_ ),
								data,
								handler,
								always_call
								);

						post_object->resolve( boost::asio::ip::tcp::resolver::query( domain, name() ) );
					}
					catch(...)
					{
						handler( "", error::value( error::critical ) );
					}
				}

				static const char* name()
				{
					return "http";
				}

			private:
				template<typename Handler>
				class post
					: public boost::enable_shared_from_this<post<Handler> >
				{
					friend class async;
					typedef Handler handler_type;

				public:
					post(
						boost::asio::io_service& io,
						boost::shared_ptr<std::string> data,
						const handler_type& handler,
						const bool is_always_call )
						: pimpl_( new post_impl( io, data, is_always_call ) )
						, socket_( io )
						, handler_( handler )
					{}

				private:
					void write()
					{
						pimpl_->write(
							socket_,
							boost::bind(
								&post::handle_write,
								shared_from_this(),
								boost::asio::placeholders::error,
								boost::asio::placeholders::bytes_transferred
								)
							);
					}

					void read()
					{
						pimpl_->read(
							socket_,
							boost::bind(
								&post::handle_read,
								shared_from_this(),
								boost::asio::placeholders::error,
								boost::asio::placeholders::bytes_transferred
								)
							);
					}

					void resolve( const boost::asio::ip::tcp::resolver::query& query )
					{		
						pimpl_->resolve(
							query,
							boost::bind(
								&post::handle_resolve,
								shared_from_this(),
								boost::asio::placeholders::error,
								boost::asio::placeholders::iterator
								)
							);
					}

					void handle_resolve(
						const boost::system::error_code& error,
						boost::asio::ip::tcp::resolver::iterator endpoint_iterator )
					{
						if ( !error ) {	
							socket_.async_connect(
								*endpoint_iterator,
								boost::bind(
									&post::handle_connect,
									shared_from_this(),
									boost::asio::placeholders::error,
									++endpoint_iterator
									)
								);

						} else {
							pimpl_->notify_error(
								error::value( error::critical, "handle_resolve", error.value() ),
								handler_
								);
						}
					}
		
					void handle_connect(
						const boost::system::error_code& error,
						boost::asio::ip::tcp::resolver::iterator endpoint_iterator )
					{
						if ( !error ) {
							write();

						} else if ( endpoint_iterator != boost::asio::ip::tcp::resolver::iterator() ) {
							socket_.close();
							socket_.async_connect(
								*endpoint_iterator,
								boost::bind(
									&post::handle_connect,
									shared_from_this(),
									boost::asio::placeholders::error,
									++endpoint_iterator
									)
								);

						} else {
							pimpl_->notify_error(
								error::value( error::critical, "handle_connect", error.value() ),
								handler_
								);
						}
					}

					void handle_write(
						const boost::system::error_code& error,
						const std::size_t bytes_transferred )
					{
						if ( !error ) {
							read();

						} else {
							pimpl_->notify_error(
								error::value( error::critical, "handle_write", error.value() ),
								handler_
								);
						}
					}

					void handle_read(
						const boost::system::error_code& error,
						const std::size_t bytes_transferred )
					{
						if ( !error ) {
							pimpl_->write_to();
							if ( pimpl_->is_always_call() )
								pimpl_->call( handler_ );
							read();

						} else if ( error == boost::asio::error::eof ) {	
							pimpl_->call( handler_ );

						} else {
							pimpl_->notify_error(
								error::value( error::critical, "handle_read", error.value() ),
								handler_
								);
						}
					}
				
					std::unique_ptr<post_impl> pimpl_;
					boost::asio::ip::tcp::socket socket_;
					handler_type handler_;
				};

				boost::asio::io_service io_;
				std::unique_ptr<boost::asio::io_service::work> work_;

				boost::thread io_runner_;
			};
		}

#ifdef OLC_OAUTH_USE_SSL
		namespace https
		{
			class sync
			{
			public:
				typedef sync_connection						connection_type;

				typedef std::map<std::string, std::string>	parameter_type;

				sync()
					: context_( io_, boost::asio::ssl::context::sslv3_client )
				{}

				std::string operator()(
					const std::string& domain,
					const std::string& method,
					const std::string& path,
					const parameter_type& parameter,
					const std::string& body,
					error::value& error )
				{
					namespace asio = boost::asio;

					try {
						std::stringstream src;
						detail::make_http_request<parameter_type>(
							src,
							method,
							path,
							parameter,
							body );

						boost::system::error_code ec;
						asio::ssl::stream<boost::asio::ip::tcp::socket> stream( io_, context_ );

						asio::ip::tcp::resolver resolver( io_ );
						asio::ip::tcp::resolver::iterator endpoint_iterator =
							resolver.resolve( asio::ip::tcp::resolver::query( domain, name() ), ec );
						const asio::ip::tcp::resolver::iterator end;
						if ( ec ) {
							error = error::value( error::critical, "resolver", ec.value() );
							return "";
						}

						ec = asio::error::host_not_found;
						while ( ec && endpoint_iterator != end )
						{
							stream.lowest_layer().close( ec );
							stream.lowest_layer().connect( *endpoint_iterator++, ec );
						}
						if ( ec ) {
							error = error::value( error::critical, "connect", ec.value() );
							return "";
						}

						stream.handshake( asio::ssl::stream_base::client, ec );
						if ( ec ) {
							error = error::value( error::critical, "handshake", ec.value() );
							return "";
						}

						const std::string data = src.str();
						asio::write( stream, asio::buffer( data.c_str(), data.size() ), asio::transfer_all(), ec );
						if ( ec ) {
							error = error::value( error::critical, "write", ec.value() );
							return "";
						}

						asio::streambuf response;
						src.str( "" );
						while( asio::read( stream, response, asio::transfer_at_least( 1 ), ec ) ) {
							src << &response;
						}

						//
						if ( const boost::optional<error::value> e = detail::check_response_code( src ) ) {
							error |= *e;
							if ( error.get_code() > error::http_status )
								return "";
						}
				
						//ヘッダ
						detail::attribute attr;
						if ( const boost::optional<error::value> e = detail::check_header( src, attr ) ) {
							error |= *e;
							if ( error.get_code() == error::critical )
								return "";
						}

						//detail::cut_header( sstream );

						// 
						if ( attr.is_chunked() ) {
							std::cout << "chunked!" << std::endl;

							return detail::cat_chunk( src );
						} else {
							return detail::read_body( src );
						}
					}
					catch(...)
					{
						error = error::value( error::critical );
						return "";
					}
				}

				static const char* name()
				{
					return "https";
				}

			private:
				boost::asio::io_service io_;
				boost::asio::ssl::context context_;
			};

			class async
			{
			public:
				typedef async_connection					connection_type;
				typedef std::map<std::string, std::string>	parameter_type;

				async()
					: context_( io_, boost::asio::ssl::context::sslv23_client )
					, work_( new boost::asio::io_service::work( io_ ) )
					, io_runner_( boost::bind( &boost::asio::io_service::run, &io_ ) )
				{}

				~async()
				{
					try {
						work_.reset();
						io_.stop();
					
						io_runner_.join();
						io_runner_.detach();
						io_.reset();
					}
					catch(...) {}
				}

				template<typename Handler>
				void operator()(
					const std::string& domain,
					const std::string& method,
					const std::string& path,
					const parameter_type& parameter,
					const std::string& body,
					Handler& handler,
					const bool always_call = false )
				{
					typedef post<Handler> post_type;

					try {
						std::stringstream src;
						detail::make_http_request<parameter_type>(
							src,
							method,
							path,
							parameter,
							body
							);

						boost::shared_ptr<std::string> data =
							boost::make_shared<std::string>(
								src.str()
								);

						boost::shared_ptr<post_type> post_object =
							boost::make_shared<post_type>(
								boost::ref( io_ ),
								boost::ref( context_ ),
								data,
								handler,
								always_call
								);

						post_object->resolve( boost::asio::ip::tcp::resolver::query( domain, name() ) );
					}
					catch(...)
					{
						handler( "", error::value( error::critical ) );
					}
				}

				static const char* name()
				{
					return "https";
				}

			private:
				template<typename Handler>
				class post
					: public boost::enable_shared_from_this<post<Handler> >
				{
					friend class async;
					typedef Handler handler_type;

				public:
					post(
						boost::asio::io_service& io,
						boost::asio::ssl::context& context,
						boost::shared_ptr<std::string> data,
						const handler_type& handler,
						const bool is_always_call )
						: pimpl_( new post_impl( io, data, is_always_call ) )
						, stream_( io, context )
						, handler_( handler )
					{}

				/*~post()
				{
					stream_.shutdown();
				}*/

				private:
					void write()
					{
						pimpl_->write(
							stream_,
							boost::bind(
								&post::handle_write,
								shared_from_this(),
								boost::asio::placeholders::error,
								boost::asio::placeholders::bytes_transferred
								)
							);
					}

					void read()
					{
						pimpl_->read(
							stream_,
							boost::bind(
								&post::handle_read,
								shared_from_this(),
								boost::asio::placeholders::error,
								boost::asio::placeholders::bytes_transferred
								)
							);
					}

					void resolve( const boost::asio::ip::tcp::resolver::query& query )
					{		
						pimpl_->resolve(
							query,
							boost::bind(
								&post::handle_resolve,
								shared_from_this(),
								boost::asio::placeholders::error,
								boost::asio::placeholders::iterator
								)
							);
					}

					void handle_resolve(
						const boost::system::error_code& error,
						boost::asio::ip::tcp::resolver::iterator endpoint_iterator )
					{
						if ( !error ) {	
							stream_.lowest_layer().async_connect(
								*endpoint_iterator,
								boost::bind(
									&post::handle_connect,
									shared_from_this(),
									boost::asio::placeholders::error,
									++endpoint_iterator
									)
								);

						} else {
							pimpl_->notify_error(
								error::value( error::critical, "handle_resolve", error.value() ),
								handler_
								);
						}
					}
		
					void handle_connect(
						const boost::system::error_code& error,
						boost::asio::ip::tcp::resolver::iterator endpoint_iterator )
					{
						if ( !error ) {
							stream_.async_handshake(
								boost::asio::ssl::stream_base::client,
								boost::bind(
									&post::handle_handshake,
									shared_from_this(),
									boost::asio::placeholders::error
									)
								);

						} else if ( endpoint_iterator != boost::asio::ip::tcp::resolver::iterator() ) {
							stream_.lowest_layer().close();
							stream_.lowest_layer().async_connect(
								*endpoint_iterator,
								boost::bind(
									&post::handle_connect,
									shared_from_this(),
									boost::asio::placeholders::error,
									++endpoint_iterator
									)
								);

						} else {
							pimpl_->notify_error(
								error::value( error::critical, "handle_connect", error.value() ),
								handler_
								);
						}
					}

					void handle_handshake( const boost::system::error_code& error )
					{
						if ( !error ) {
							write();

						} else {
							pimpl_->notify_error(
								error::value( error::critical, "handle_handshake", error.value() ),
								handler_
								);
						}
					}

					void handle_write(
						const boost::system::error_code& error,
						const std::size_t bytes_transferred )
					{
						if ( !error ) {
							read();

						} else {
							pimpl_->notify_error(
								error::value( error::critical, "handle_write", error.value() ),
								handler_
								);
						}
					}

					void handle_read(
						const boost::system::error_code& error,
						const std::size_t bytes_transferred )
					{
						if ( !error ) {
							pimpl_->write_to();
							if ( pimpl_->is_always_call() )
								pimpl_->call( handler_ );
							read();

						} else if ( error == boost::asio::error::eof || error == boost::asio::error::shut_down ) {	
							pimpl_->call( handler_ );

						} else {
							pimpl_->notify_error(
								error::value( error::critical, "handle_read", error.value() ),
								handler_
								);
						}
					}
				
					std::unique_ptr<post_impl> pimpl_;
					boost::asio::ssl::stream<boost::asio::ip::tcp::socket> stream_;
					handler_type handler_;
				};

				boost::asio::io_service io_;
				boost::asio::ssl::context context_;
				std::unique_ptr<boost::asio::io_service::work> work_;

				boost::thread io_runner_;
			};
		} // - https
#endif
	} // - protocol
} // - webapp

#endif /*YUTOPP_PROTOCOL_HPP*/